Terrain System
Here's the translated markdown:
Attribution
This text is derived and translated to English from docs/TerrainSystem.md from the Survivalcraft API Gitee Repository.
This document provides a detailed explanation of the overall architecture, core mechanisms, data structures, and runtime flow of the Survivalcraft terrain system.
Table of Contents¶
- Overall Architecture Overview
- Core Data Structures
- Terrain Update and Generation Mechanism
- Terrain Editing Mechanism
- Terrain Rendering Mechanism
- Terrain Serialization and Storage
- Core Flowcharts
- Biome Generation Mechanism
1. Overall Architecture Overview¶
1.1 System Architecture Diagram¶
The terrain system uses a layered architecture with clearly defined module responsibilities:
graph TB
subgraph "Game Layer"
ST[SubsystemTerrain<br/>Terrain Subsystem Entry]
end
subgraph "Core Management Layer"
TU[TerrainUpdater<br/>Update Manager]
TR[TerrainRenderer<br/>Render Manager]
TS[TerrainSerializer23<br/>Serializer]
end
subgraph "Data Layer"
T[Terrain<br/>Terrain Container]
TC[TerrainChunk<br/>Terrain Chunk]
TG[TerrainGeometry<br/>Geometry Data]
end
subgraph "Generation Layer"
ITG[ITerrainContentsGenerator<br/>Content Generator Interface]
TG24[TerrainContentsGenerator24<br/>Standard Generator]
TGF[TerrainContentsGeneratorFlat<br/>Flat Generator]
end
ST --> TU
ST --> TR
ST --> TS
ST --> T
ST --> ITG
TU --> TC
TR --> TG
TS --> TC
ITG --> TG24
ITG --> TGF
T --> TC
TC --> TG
1.2 Core Module Responsibilities¶
| Module | Responsibility | Key Characteristics |
|---|---|---|
| SubsystemTerrain | Terrain system entry point, integrates all terrain functionality | Implements IUpdateable and IDrawable, manages raycasting and block change notifications |
| Terrain | Terrain data container, manages all Chunks | Uses a hash table to store Chunks, provides coordinate conversion and cell access interfaces |
| TerrainChunk | Terrain chunk, stores actual block data | 16×16×256 cells, contains Shaft data for temperature/humidity/height |
| TerrainUpdater | Terrain update manager | Multi-threaded updates, state machine driven, priority scheduling |
| TerrainRenderer | Terrain renderer | Multi-pass rendering (opaque/alpha-tested/transparent), supports fog fade-in |
| TerrainSerializer23 | Terrain serializer | RLE compression + Deflate compression, region file storage |
| ITerrainContentsGenerator | Terrain content generator interface | Four-phase generation, supports different generation modes |
1.3 Chunk State Machine¶
The state transitions of terrain chunks are key to understanding the entire terrain system:
stateDiagram-v2
[*] --> NotLoaded: New Chunk Allocated
NotLoaded --> InvalidContents1: Load Failed / New Chunk
NotLoaded --> InvalidLight: Successfully Loaded from Save
InvalidContents1 --> InvalidContents2: Pass1 Complete
InvalidContents2 --> InvalidContents3: Pass2 Complete
InvalidContents3 --> InvalidContents4: Pass3 Complete
InvalidContents4 --> InvalidLight: Pass4 Complete
InvalidLight --> InvalidPropagatedLight: Sunlight Calculation Complete
InvalidPropagatedLight --> InvalidVertices1: Light Propagation Complete
InvalidVertices1 --> InvalidVertices2: Vertex Generation Pass1
InvalidVertices2 --> Valid: Vertex Generation Pass2
Valid --> InvalidLight: Block Change / Sky Brightness Change
Valid --> InvalidVertices1: Season Change / Brightness Setting Change
AnyState --> NotLoaded: Player Moved Away, Chunk Released
State Descriptions:
| State | Meaning | Trigger Condition |
|---|---|---|
NotLoaded |
Not loaded | Chunk just allocated or load failed |
InvalidContents1-4 |
Contents invalid (four phases) | Terrain contents need to be generated |
InvalidLight |
Lighting invalid | Sunlight and height need to be recalculated |
InvalidPropagatedLight |
Propagated lighting invalid | Light sources need to be propagated |
InvalidVertices1-2 |
Vertices invalid | Render mesh needs to be regenerated |
Valid |
Fully valid | Can be rendered normally |
2. Core Data Structures¶
2.1 Terrain — Terrain Container¶
Terrain is the top-level container of the terrain system, responsible for managing all TerrainChunks:
public class Terrain : IDisposable {
// Chunk storage structure (open-addressing hash table)
public class ChunksStorage {
public const int Capacity = 65536; // Maximum number of Chunks
public TerrainChunk[] m_array; // Hash table array
}
public ChunksStorage m_allChunks; // Storage for all Chunks
public HashSet<TerrainChunk> m_allocatedChunks; // Set of allocated Chunks
// Cell value encoding constants
public const int ContentsMask = 1023; // Low 10 bits: block ID
public const int LightMask = 15360; // 4 bits: light value
public const int DataMask = -16384; // High 18 bits: block data
}
Key Methods:
GetChunkAtCell(x, y, z)— Get Chunk by world coordinateGetCellValue(x, y, z)— Get full cell value (with bounds check)GetCellValueFast(x, y, z)— Fast cell value retrieval (no bounds check)SetCellValueFast(x, y, z, value)— Fast cell value set
Coordinate Conversion:
// World coordinate -> Chunk coordinate
chunkX = x >> TerrainChunk.SizeBits; // SizeBits = 4
chunkZ = z >> TerrainChunk.SizeBits;
// World coordinate -> Local coordinate within Chunk
localX = x & 0xF; // Equivalent to x % 16
localZ = z & 0xF;
2.2 TerrainChunk — Terrain Chunk¶
TerrainChunk is the core data unit of the terrain system:
public class TerrainChunk : IDisposable {
// Size constants
public const int SizeBits = 4; // 16 = 2^4
public const int Size = 16; // Chunk horizontal size
public const int HeightBits = 8; // 256 = 2^8
public const int Height = 256; // Chunk height
public const int SliceHeight = 16; // Slice height
public const int SlicesCount = 16; // Number of slices (256/16)
// Core data
public Terrain Terrain; // Owning terrain
public Point2 Coords; // Chunk coordinates
public Point2 Origin; // World origin coordinates
public BoundingBox BoundingBox; // Bounding box
// Block data (uses object pool cache)
public int[] Cells; // 65,536 cell values
public int[] Shafts; // 256 Shaft values (temperature/humidity/height)
// Render data
public TerrainChunkGeometry Geometry;
public TerrainGeometry[] ChunkSliceGeometries; // 16 slice geometries
public DynamicArray<TerrainChunkGeometry.Buffer> Buffers;
// State
public TerrainChunkState State; // Main thread state
public TerrainChunkState ThreadState; // Update thread state
public volatile bool NewGeometryData; // New geometry data flag
}
Cell Index Calculation:
There are two implementations in the source code:
// Method 1: Bitwise (used by CalculateCellIndex, supports out-of-range Y)
public static int CalculateCellIndex(int x, int y, int z) {
return y | (x << HeightBits) | (z << 12); // HeightBits=8, 12=8+4
}
// Method 2: Multiplication (used by GetCellValueFast/SetCellValueFast)
public int GetCellValueFast(int x, int y, int z) {
return Cells[y + x * Height + z * Height * Size]; // Height=256, Size=16
}
Both methods are mathematically equivalent.
Shaft Value Encoding (one per column, stores environmental information):
Shaft Value Bit Layout
| 31–24 | 23–16 | 15–12 | 11–8 | 7–0 |
|---|---|---|---|---|
| SunlightHeight | BottomHeight | Humidity | Temperature | TopHeight |
int shaftValue = GetShaftValue(x, z);
int topHeight = ExtractTopHeight(shaftValue); // Surface height
int bottomHeight = ExtractBottomHeight(shaftValue); // Bottom height
int sunlightHeight = ExtractSunlightHeight(shaftValue); // Sunlight penetration height
int temperature = ExtractTemperature(shaftValue); // Temperature
int humidity = ExtractHumidity(shaftValue); // Humidity
2.3 Cell Value Encoding¶
Each cell uses a 32-bit integer to store complete block information:
Cell Value Bit Layout
| 31–14 | 13–10 | 9–0 |
|---|---|---|
| Data | Light | Contents |
int cellValue = GetCellValue(x, y, z);
// Extract data
int contents = Terrain.ExtractContents(cellValue); // Block ID (0–1023)
int light = Terrain.ExtractLight(cellValue); // Light value (0–15)
int data = Terrain.ExtractData(cellValue); // Block data
// Build cell value
int value = Terrain.MakeBlockValue(contents, light, data);
// Replace individual fields
value = Terrain.ReplaceLight(cellValue, newLight);
value = Terrain.ReplaceContents(cellValue, newContents);
2.4 TerrainVertex — Terrain Vertex¶
public struct TerrainVertex {
public float X, Y, Z; // Position (12 bytes)
public short Tx, Ty; // Texture coordinates (4 bytes, normalized)
public Color Color; // Vertex color (4 bytes, includes lighting)
// Vertex declaration
public static readonly VertexDeclaration VertexDeclaration = new(
new VertexElement(0, VertexElementFormat.Vector3, VertexElementSemantic.Position),
new VertexElement(12, VertexElementFormat.NormalizedShort2, VertexElementSemantic.TextureCoordinate),
new VertexElement(16, VertexElementFormat.NormalizedByte4, VertexElementSemantic.Color)
);
}
Vertex size: 20 bytes per vertex
2.5 TerrainGeometry — Geometry Data¶
public class TerrainGeometry : IDisposable {
// Seven subsets, categorized by render type and facing direction
public TerrainGeometrySubset[] Subsets; // 7 subsets
// Convenient accessors
public TerrainGeometrySubset SubsetOpaque; // Opaque (subset 4)
public TerrainGeometrySubset SubsetAlphaTest; // Alpha-tested (subset 5)
public TerrainGeometrySubset SubsetTransparent; // Transparent (subset 6)
// Opaque subsets by facing direction (used for face culling optimization)
public TerrainGeometrySubset[] OpaqueSubsetsByFace; // 6 directions
// Multi-texture support
public Dictionary<Texture2D, TerrainGeometry> Draws;
public Texture2D DefaultTexture;
}
Subset Classification:
| Index | Type | Purpose |
|---|---|---|
| 0–3 | Opaque (by direction) | Face culling optimization |
| 4 | Opaque (general) | Blocks that don't need face culling |
| 5 | Alpha-tested | Leaves, fences, etc. |
| 6 | Transparent | Water, glass, etc. |
3. Terrain Update and Generation Mechanism¶
3.1 TerrainUpdater Architecture¶
TerrainUpdater is the core of terrain updates, using a multi-threaded architecture:
graph LR
subgraph "Main Thread"
U[Update]
PFD[PrepareForDrawing]
SRS[SendReceiveChunkStates]
end
subgraph "Update Thread"
TUF[ThreadUpdateFunction]
SUF[SynchronousUpdateFunction]
US[UpdateChunkSingleStep]
end
subgraph "Sync Mechanism"
UE[AutoResetEvent<br/>Update Event]
PE[ManualResetEvent<br/>Pause Event]
UL[Lock<br/>Parameter Lock]
end
U --> SRS
U --> UE
PFD --> UE
TUF --> UE
TUF --> PE
TUF --> SUF
SUF --> US
US --> UE
3.2 Update Location Management¶
The system supports multiple update locations (e.g., one per player in multiplayer):
public struct UpdateLocation {
public Vector2 Center; // Update center
public Vector2? LastChunksUpdateCenter; // Last update center
public float VisibilityDistance; // Visibility distance
public float ContentDistance; // Content generation distance
}
// Set update location from main thread
SetUpdateLocation(playerIndex, center, visibilityDistance, contentDistance);
3.3 Chunk Allocation and Release¶
flowchart TD
A[Iterate Allocated Chunks] --> B{Within Update Range?}
B -- No --> C[Save and Release]
B -- Yes --> D[Keep]
E[Iterate Update Range] --> F{Chunk Exists?}
F -- No --> G[Allocate New Chunk]
F -- Yes --> H[Check Coordinate Correctness]
C --> I[Save to Archive]
I --> J[Call OnChunkDiscarding]
J --> K[FreeChunk]
G --> L[AllocateChunk]
L --> M[Downgrade Neighbor States]
3.4 State Update Flow¶
Single-Step Update (UpdateChunkSingleStep):
void UpdateChunkSingleStep(TerrainChunk chunk, int skylightValue) {
switch (chunk.ThreadState) {
case TerrainChunkState.NotLoaded:
// Try to load from save
if (TerrainSerializer.LoadChunk(chunk))
chunk.ThreadState = TerrainChunkState.InvalidLight;
else
chunk.ThreadState = TerrainChunkState.InvalidContents1;
break;
case TerrainChunkState.InvalidContents1:
TerrainContentsGenerator.GenerateChunkContentsPass1(chunk);
chunk.ThreadState = TerrainChunkState.InvalidContents2;
break;
// ... other state handling
case TerrainChunkState.InvalidVertices1:
// Wait for neighbors to be ready
CalculateChunkSliceContentsHashes(chunk);
GenerateChunkVertices(chunk, 0); // Odd slices, ultimately used for rendering
chunk.ThreadState = TerrainChunkState.InvalidVertices2;
break;
case TerrainChunkState.InvalidVertices2:
GenerateChunkVertices(chunk, 1); // Even slices, ultimately used for rendering
chunk.NewGeometryData = true; // Notify renderer
chunk.ThreadState = TerrainChunkState.Valid;
break;
}
}
3.5 Terrain Content Generation¶
The ITerrainContentsGenerator interface defines a four-phase generation process:
public interface ITerrainContentsGenerator {
int OceanLevel { get; }
// Environment queries
Vector3 FindCoarseSpawnPosition();
float CalculateOceanShoreDistance(float x, float z);
float CalculateHeight(float x, float z);
int CalculateTemperature(float x, float z);
int CalculateHumidity(float x, float z);
// Four-phase generation
void GenerateChunkContentsPass1(TerrainChunk chunk); // Terrain height, temperature, humidity
void GenerateChunkContentsPass2(TerrainChunk chunk); // Detail generation
void GenerateChunkContentsPass3(TerrainChunk chunk); // Caves, minerals
void GenerateChunkContentsPass4(TerrainChunk chunk); // Vegetation, decorations
}
TerrainContentsGenerator24 Generation Steps:
| Pass | Step | Content |
|---|---|---|
| 1 | GenerateSurfaceParameters | Calculate temperature, humidity (upper half) |
| 1 | GenerateTerrain | Generate base terrain (upper half) |
| 2 | GenerateSurfaceParameters | Calculate temperature, humidity (lower half) |
| 2 | GenerateTerrain | Generate base terrain (lower half) |
| 3 | GenerateCaves | Generate caves |
| 3 | GeneratePockets | Generate dirt, gravel pockets, etc. |
| 3 | GenerateMinerals | Generate minerals (coal, iron, diamond, etc.) |
| 3 | GenerateSurface | Generate surface blocks |
| 3 | PropagateFluidsDownwards | Propagate fluids downward |
| 4 | GenerateGrassAndPlants | Generate grass and plants |
| 4 | GenerateTrees | Generate trees |
| 4 | GenerateSnowAndIce | Generate snow and ice |
| 4 | GenerateBedrockAndAir | Generate bedrock and air |
3.6 Lighting System¶
Sunlight Calculation (GenerateChunkSunLightAndHeight):
flowchart TD
A[Scan Downward from Top] --> B{Non-Air Block Encountered?}
B -- No --> C[Set Sky Lighting]
C --> A
B -- Yes --> D[Record TopHeight]
D --> E[Scan Upward from Bottom]
E --> F{Transparent Block Encountered?}
F -- No --> G[Set Light to 0]
G --> E
F -- Yes --> H[Record BottomHeight]
H --> I[Calculate Light Attenuation Downward from TopHeight]
I --> J{Block Transparent?}
J -- Yes --> K[Attenuate Light Value]
K --> I
J -- No --> L[Set Bottom Light to 0]
Light Source Propagation:
// Collect light sources
void GenerateChunkLightSources(TerrainChunk chunk) {
// 1. Collect self-luminous block sources
// 2. Collect light propagated from neighboring Chunks
}
// Propagate light sources
void PropagateLightSources() {
foreach (var lightSource in m_lightSources) {
// Propagate in 6 directions, attenuating through transparent blocks
PropagateLightSource(x-1, y, z, light);
PropagateLightSource(x+1, y, z, light);
PropagateLightSource(x, y-1, z, light);
PropagateLightSource(x, y+1, z, light);
PropagateLightSource(x, y, z-1, light);
PropagateLightSource(x, y, z+1, light);
}
}
4. Terrain Editing Mechanism¶
4.1 TerrainBrush — Terrain Brush¶
TerrainBrush is used for bulk terrain editing and supports various shapes:
public class TerrainBrush {
public struct Cell {
public sbyte X, Y, Z; // Relative coordinates
public int Value; // Block value
}
// Brush definition
public class Brush {
public int m_value; // Fixed value
public Func<int?, int?> m_handler1; // Handler based on current value
public Func<Point3, int?> m_handler2; // Handler based on position
}
// Counter
public class Counter {
public int m_value; // Target value
public Func<int?, int> m_handler1; // Counter based on current value
public Func<Point3, int> m_handler2; // Counter based on position
}
private Dictionary<int, Cell> m_cellsDictionary; // Cell dictionary
private Cell[] m_cells; // Compiled array
}
Key Methods:
// Add cells
AddCell(x, y, z, brush);
AddBox(x, y, z, sizeX, sizeY, sizeZ, brush);
AddRay(x1, y1, z1, x2, y2, z2, sizeX, sizeY, sizeZ, brush);
// Paint to terrain
PaintFast(chunk, x, y, z); // Direct paint
PaintFastSelective(chunk, x, y, z, onlyValue); // Selective paint
PaintFastAvoidWater(chunk, x, y, z); // Paint avoiding water
Paint(subsystemTerrain, x, y, z); // Paint with notifications
4.2 SubsystemTerrain Change Interface¶
// Modify a single block (with notification)
void ChangeCell(int x, int y, int z, int value,
bool updateModificationCounter = true, MovingBlock movingBlock = null) {
// 1. Mod hook
// 2. Bounds check
// 3. Compare old value
// 4. Set new value
// 5. Increment modification counter
// 6. Downgrade neighboring Chunk states
// 7. Record modification location
// 8. Trigger block behaviors
}
// Destroy block (with drops and particles)
void DestroyCell(int toolLevel, int x, int y, int z, int newValue,
bool noDrop, bool noParticleSystem, MovingBlock movingBlock = null);
Modification Notification Flow:
sequenceDiagram
participant ST as SubsystemTerrain
participant T as Terrain
participant TU as TerrainUpdater
participant SBB as SubsystemBlockBehaviors
ST->>T: SetCellValueFast
ST->>TU: DowngradeChunkNeighborhoodState
Note over TU: Downgrade to InvalidLight
ST->>ST: Record to m_modifiedCells
loop Each Frame Update
ST->>ST: ProcessModifiedCells
ST->>SBB: OnNeighborBlockChanged
end
5. Terrain Rendering Mechanism¶
Triangle vertex data is generated in the TerrainUpdater.GenerateChunkVertices method, which calls Block.GenerateTerrainVertices for each block. To see how a specific block generates its vertex data, refer to that block's implementation of this method.
The following describes the specific flow of TerrainRenderer.
5.1 Rendering Flow¶
sequenceDiagram
participant C as Camera
participant TR as TerrainRenderer
participant TU as TerrainUpdater
participant GPU as GPU
Note over C: Draw Begins
TR->>TU: PrepareForDrawing
TU->>TU: SetUpdateLocation
TR->>TR: PrepareForDrawing
loop Each Chunk
alt Has New Geometry Data
TR->>GPU: SetupTerrainChunkGeometryVertexIndexBuffers
end
alt In View Frustum
TR->>TR: Add to m_chunksToDraw
end
end
TR->>TR: DrawOpaque
loop Each Chunk
TR->>GPU: DrawIndexed (Opaque)
end
TR->>TR: DrawAlphaTested
loop Each Chunk
TR->>GPU: DrawIndexed (Alpha-Tested)
end
Note over C: Transparent Objects Pass
TR->>TR: DrawTransparent
loop Each Chunk
TR->>GPU: DrawIndexed (Transparent)
end
5.2 Geometry Data Compilation¶
static void CompileDrawSubsets(TerrainGeometry[] chunkSliceGeometries,
DynamicArray<TerrainChunkGeometry.Buffer> buffers) {
// 1. Count vertex/index counts per texture
// 2. Create Buffers grouped by texture
// 3. Set each subset's range within the Buffer
// 4. Write vertex and index data to GPU Buffer
}
Buffer Structure:
public class Buffer : IDisposable {
public VertexBuffer VertexBuffer; // Vertex buffer
public IndexBuffer IndexBuffer; // Index buffer
public Texture2D Texture; // Associated texture
// Ranges for seven subsets
public int[] SubsetIndexBufferStarts = new int[7];
public int[] SubsetIndexBufferEnds = new int[7];
public int[] SubsetVertexBufferStarts = new int[7];
public int[] SubsetVertexBufferEnds = new int[7];
}
5.3 Render States¶
| Pass | Blend Mode | Depth Test | Cull Mode | Shader |
|---|---|---|---|---|
| DrawOpaque | Opaque | Read/Write | CCW Cull | Opaque.vsh/psh |
| DrawAlphaTested | Opaque | Read/Write | CCW Cull | AlphaTested.vsh/psh |
| DrawTransparent | AlphaBlend | Read/Write | CW when Underwater | Transparent.vsh/psh |
5.4 Face Culling Optimization¶
// Determine subsets to render based on camera position
int subsetsMask = 16; // Render center subset by default
if (viewPosition.Z > chunk.BoundingBox.Min.Z) subsetsMask |= 1; // +Z face
if (viewPosition.X > chunk.BoundingBox.Min.X) subsetsMask |= 2; // +X face
if (viewPosition.Z < chunk.BoundingBox.Max.Z) subsetsMask |= 4; // -Z face
if (viewPosition.X < chunk.BoundingBox.Max.X) subsetsMask |= 8; // -X face
DrawTerrainChunkGeometrySubsets(shader, chunk, subsetsMask);
5.5 Chunk Fade-In Effect¶
// Start fade-in
void StartChunkFadeIn(Camera camera, TerrainChunk chunk) {
// Calculate minimum distance from Chunk corners to camera
float hazeEnd = Math.Min(distances);
chunk.HazeEnds[gameWidgetIndex] = hazeEnd;
}
// Per-frame fade-in
void RunChunkFadeIn(Camera camera, TerrainChunk chunk) {
chunk.HazeEnds[gameWidgetIndex] += 32f * Time.FrameDuration;
if (hazeEnd >= VisibilityRange) {
// Fade-in complete
}
}
6. Terrain Serialization and Storage¶
6.1 Storage Architecture¶
The system supports two storage methods:
public interface IStorage : IDisposable {
void Open(string directoryName, string suffix);
int Load(Point2 coords, byte[] buffer);
void Save(Point2 coords, byte[] buffer, int size);
}
| Storage Method | File Structure | Characteristics |
|---|---|---|
| SingleFileStorage | Chunks32fs.dat | Single file, linked-list node structure |
| RegionFileStorage | Regions/Region X,Y.dat | Region files, 256 Chunks per file |
RegionFileStorage is used by default, with the following structure:
World/
└── Regions/
├── Region 0,0.dat # Contains Chunks (0,0) to (15,15)
├── Region 0,1.dat
├── Region 1,0.dat
└── ...
6.2 Data Compression¶
flowchart LR
A[Chunk Data] --> B[RLE Encoding]
B --> C[Deflate Compression]
C --> D[Storage]
D --> E[Read]
E --> F[Deflate Decompression]
F --> G[RLE Decoding]
G --> H[Chunk Data]
RLE Encoding Format:
// Short format (count ≤ 15): 4 bytes
// | Light = count-1 | Data | Contents |
// Long format (count > 15): 5 bytes
// | Light = 15 | Data | Contents | extraCount = count - 16 |
Compression Example:
int CompressChunkData(TerrainChunk chunk, byte[] buffer) {
// 1. Write Shaft data (temperature/humidity)
for (int i = 0; i < 16; i++) {
for (int j = 0; j < 16; j++) {
int shaft = chunk.GetShaftValueFast(i, j);
buffer[pos++] = (byte)((temperature << 4) | humidity);
}
}
// 2. RLE encode cell data
// 3. Deflate compress
return Deflate(compressBuffer, 0, pos, outputBuffer);
}
6.3 Incremental Saving¶
void SaveChunk(TerrainChunk chunk) {
if (chunk.State > InvalidContents4 && chunk.ModificationCounter > 0) {
SaveChunkData(chunk);
chunk.ModificationCounter = 0;
}
}
Only modified Chunks are saved, avoiding redundant writes.
7. Core Flowcharts¶
7.1 Complete Update Flow¶
flowchart TB
subgraph "Main Thread"
direction TB
A[SubsystemTerrain.Update] --> B[TerrainUpdater.Update]
B --> C{Sky Brightness Changed?}
C -- Yes --> D[Downgrade All Chunks to InvalidLight]
C -- No --> E{Season Changed?}
E -- Yes --> F[Downgrade All Chunks to InvalidVertices1]
E -- No --> G[Process Update Locations]
G --> H[Allocate / Release Chunks]
H --> I[Send / Receive Chunk States]
I --> J[Notify Block Behaviors]
end
subgraph "Update Thread"
direction TB
K[ThreadUpdateFunction] --> L[SynchronousUpdateFunction]
L --> M[FindBestChunkToUpdate]
M --> N[UpdateChunkSingleStep]
N --> O{Target State Reached?}
O -- No --> N
O -- Yes --> P{More Chunks?}
P -- Yes --> M
P -- No --> Q[Wait for Next Frame]
end
7.2 Chunk Lifecycle¶
flowchart TD
A[Player Approaches] --> B[AllocateChunk]
B --> C[State = NotLoaded]
C --> D{Load from Save?}
D -- Success --> E[State = InvalidLight]
D -- Fail --> F[Begin Generation]
F --> G[Pass1: Base Terrain]
G --> H[Pass2: Details]
H --> I[Pass3: Caves & Minerals]
I --> J[Pass4: Vegetation & Decorations]
J --> E
E --> K[Calculate Sunlight and Height]
K --> L[Propagate Light Sources]
L --> M[Generate Vertices Pass1]
M --> N[Generate Vertices Pass2]
N --> O[State = Valid]
O --> P{Player Moved Away?}
P -- No --> Q{Block Changed?}
Q -- Yes --> R[Downgrade State]
R --> K
Q -- No --> O
P -- Yes --> S[SaveChunk]
S --> T[OnChunkDiscarding]
T --> U[FreeChunk]
7.3 Block Change Flow¶
sequenceDiagram
participant Game as Game Logic
participant ST as SubsystemTerrain
participant T as Terrain
participant TC as TerrainChunk
participant TU as TerrainUpdater
participant SBB as SubsystemBlockBehaviors
Game->>ST: ChangeCell(x, y, z, value)
ST->>ST: Mod Hook Check
ST->>T: GetCellValueFast
T-->>ST: oldValue
ST->>T: SetCellValueFast
ST->>TC: ModificationCounter++
ST->>TU: DowngradeChunkNeighborhoodState
Note over TU: Downgrade to InvalidLight<br/>Range = 1
ST->>ST: Record to m_modifiedCells
Note over ST: Next Frame Update
ST->>ST: ProcessModifiedCells
loop Each Modified Cell
ST->>SBB: OnNeighborBlockChanged
end
Appendix: Key Class Relationship Diagram¶
classDiagram
class SubsystemTerrain {
+Terrain Terrain
+TerrainUpdater TerrainUpdater
+TerrainRenderer TerrainRenderer
+TerrainSerializer23 TerrainSerializer
+ITerrainContentsGenerator TerrainContentsGenerator
+ChangeCell()
+DestroyCell()
+Raycast()
}
class Terrain {
+ChunksStorage m_allChunks
+HashSet~TerrainChunk~ m_allocatedChunks
+AllocateChunk()
+FreeChunk()
+GetCellValue()
+SetCellValueFast()
}
class TerrainChunk {
+Point2 Coords
+Point2 Origin
+int[] Cells
+int[] Shafts
+TerrainChunkState State
+TerrainGeometry[] ChunkSliceGeometries
}
class TerrainUpdater {
+UpdateLocation[] Locations
+UpdateChunkSingleStep()
+DowngradeChunkNeighborhoodState()
}
class TerrainRenderer {
+DrawOpaque()
+DrawAlphaTested()
+DrawTransparent()
}
class ITerrainContentsGenerator {
<<interface>>
+GenerateChunkContentsPass1()
+GenerateChunkContentsPass2()
+GenerateChunkContentsPass3()
+GenerateChunkContentsPass4()
+CalculateTemperature()
+CalculateHumidity()
+CalculateMountainRangeFactor()
}
class TerrainBrush {
+Cell[] Cells
+Paint()
+AddBox()
+AddRay()
}
class PlantsManager {
<<static>>
+m_treeBrushesByType
+m_treeTrunksByType
+m_treeLeavesByType
+GenerateRandomTreeType()
+GenerateRandomPlantValue()
+CalculateTreeProbability()
+GetTreeBrushes()
}
class WorldSettings {
+BiomeSize
+TemperatureOffset
+HumidityOffset
+TerrainGenerationMode
+IslandSize
}
SubsystemTerrain --> Terrain
SubsystemTerrain --> TerrainUpdater
SubsystemTerrain --> TerrainRenderer
SubsystemTerrain --> ITerrainContentsGenerator
Terrain --> TerrainChunk
TerrainUpdater --> TerrainChunk
TerrainRenderer --> TerrainChunk
ITerrainContentsGenerator --> TerrainBrush
ITerrainContentsGenerator --> TerrainChunk
ITerrainContentsGenerator --> PlantsManager
ITerrainContentsGenerator --> WorldSettings
PlantsManager --> TerrainBrush
8. Biome Generation Mechanism¶
Survivalcraft has no explicit "Biome" class. Instead, it implicitly defines different terrain region types through combinations of temperature, humidity, and mountain factor parameters. This design allows for naturally smooth transitions between biomes.
8.1 Core Environmental Parameters¶
flowchart LR
subgraph "Noise Inputs"
S[World Seed]
TO[m_temperatureOffset]
HO[m_humidityOffset]
MO[m_mountainsOffset]
end
subgraph "Simplex Noise Generation"
TN[Temperature Noise<br/>Frequency: 0.0015/BiomeSize]
HN[Humidity Noise<br/>Frequency: 0.0012/BiomeSize]
MN[Mountain Noise<br/>Frequency: 0.0006/BiomeSize]
end
subgraph "Output Parameters"
T[Temperature<br/>Range: 0–15]
H[Humidity<br/>Range: 0–15]
M[Mountain Factor<br/>Range: 0–1]
end
S --> TN
S --> HN
S --> MN
TO --> TN
HO --> HN
MO --> MN
TN --> T
HN --> H
MN --> M
Parameter Calculation Formulas¶
// Temperature calculation (range 0–15)
int CalculateTemperature(float x, float z) {
return Math.Clamp(
(int)(MathUtils.Saturate(
3f * SimplexNoise.OctavedNoise(
x + m_temperatureOffset.X,
z + m_temperatureOffset.Y,
0.0015f / TGBiomeScaling, // frequency
5, // octave count
2f, // amplitude multiplier
0.6f // persistence
) - 1.1f + m_worldSettings.TemperatureOffset / 16f
) * 16f),
0, 15
);
}
// Humidity calculation (range 0–15)
int CalculateHumidity(float x, float z) {
return Math.Clamp(
(int)(MathUtils.Saturate(
3f * SimplexNoise.OctavedNoise(
x + m_humidityOffset.X,
z + m_humidityOffset.Y,
0.0012f / TGBiomeScaling,
5, 2f, 0.6f
) - 0.9f + m_worldSettings.HumidityOffset / 16f
) * 16f),
0, 15
);
}
// Mountain factor calculation (range 0–1)
float CalculateMountainRangeFactor(float x, float z) {
return SimplexNoise.OctavedNoise(
x + m_mountainsOffset.X,
z + m_mountainsOffset.Y,
TGMountainRangeFreq / TGBiomeScaling, // default 0.0006/BiomeSize
3, 1.91f, 0.75f, true
);
}
Adjustable Parameters (WorldSettings)¶
| Parameter | Description | Default |
|---|---|---|
BiomeSize |
Biome size multiplier | 1.0 |
TemperatureOffset |
Global temperature offset | 0 |
HumidityOffset |
Global humidity offset | 0 |
8.2 Biome Types and Conditions¶
The game implicitly defines the following biome types through parameter combinations:
flowchart TD
A[Position x, z] --> B{Temp > 8?<br/>Humidity < 8?<br/>Mountain Factor < 0.97?}
B -- Yes --> C[Desert / Beach<br/>Sand Base]
B -- No --> D{Temp ≤ 8?}
D -- Yes --> E[Cold Region<br/>Snow, Ice]
D -- No --> F{Humidity Range?}
F -- Humidity ≥ 6 --> G[Forest / Grassland<br/>Grass Base]
F -- Humidity < 6 --> H[Arid Plains<br/>Grass / Sand]
C --> I{Shore Distance < 16?}
I -- Yes --> J[Beach]
I -- No --> K[Desert]
E --> L{Height > 120?}
L -- Yes --> M[Alpine Snow]
L -- No --> N[Tundra / Permafrost]
Biome Determination Logic¶
| Biome | Temperature | Humidity | Mountain Factor | Other Conditions |
|---|---|---|---|---|
| Desert | > 8 | < 8 | < 0.97 | Distance to ocean > 16 blocks |
| Beach | > 8 | < 8 | < 0.97 | Distance to ocean ≤ 16 blocks |
| Tundra | ≤ 8 | Any | Any | Height ≤ 120 |
| Alpine Snow | ≤ 8 | Any | Any | Height > 120 |
| Forest | 4–15 | ≥ 6 | < 0.9 | — |
| Grassland | 4–15 | ≥ 6 | ≥ 0.9 | — |
| Arid Plains | > 4 | < 6 | < 0.9 | — |
8.3 Surface Block Generation¶
In the GenerateSurface method, the surface block type is determined by environmental parameters:
void GenerateSurface(TerrainChunk chunk) {
// Iterate over each horizontal position
for (int i = 0; i < 16; i++) {
for (int j = 0; j < 16; j++) {
int temperature = terrain.GetTemperature(x, z);
int humidity = terrain.GetHumidity(x, z);
// Determine surface block based on temperature and height
if (height > 120 && IsPlaceFrozen(temperature, height)) {
surfaceBlock = 62; // Ice
}
else if (temperature > 4 && temperature < 7) {
surfaceBlock = 6; // Gravel (transition zone)
}
else {
surfaceBlock = 7; // Sand (desert)
}
// ... more conditions
}
}
}
Block ID Reference:
| ID | Block | Generation Condition |
|---|---|---|
| 2 | Grass | Default surface |
| 3 | Dirt | Underground |
| 4 | Sand | Desert / Coast |
| 6 | Gravel | Transition zone |
| 7 | Sand (Desert) | High temp, low humidity |
| 8 | Grass (Tall) | High humidity |
| 61 | Snow | Cold surface |
| 62 | Ice | Cold water body |
| 66 | Sandstone | Special conditions |
| 72 | Wet Sand | Near water |
8.4 Vegetation Generation¶
Vegetation generation occurs in Pass 4, selecting appropriate vegetation types based on temperature and humidity.
Tree Type Selection¶
graph LR
subgraph "Temperature-Humidity Matrix"
T0["Temperature 0"] --> S["Spruce<br/>Tall Spruce"]
T4["Temperature 4"] --> O["Oak"]
T8["Temperature 8"] --> B["Birch"]
T12["Temperature 12"] --> P["Poplar"]
end
subgraph "Special Conditions"
LowH["Humidity < 6"] --> M["Mimosa<br/>(Desert Edge)"]
HighY["Height > 85"] --> TP["Poplar"]
end
Tree Generation Probability Function¶
PlantsManager.CalculateTreeProbability defines the spawn probability for each tree type:
| Tree Type | Temperature Range | Humidity Range | Height Range | Notes |
|---|---|---|---|---|
| Oak | 4–10 optimal | 6+ | < 82 | Warm, humid regions |
| Birch | 5–11 | Any | < 82 | Temperate regions |
| Spruce | 0–6 (cold) | 3–12 | Any | Cold regions |
| Tall Spruce | 0–6 | 9–15 | < 95 | High-altitude, humid, cold |
| Mimosa | 2–12 | 0–4 | Any | Dry, warm regions |
| Poplar | 4–12 | 3+ | 85–92 | Mid-to-high altitude |
// Probability calculation example: Oak
float CalculateTreeProbability(TreeType.Oak, int temperature, int humidity, int y) {
return RangeProbability(temperature, 4f, 10f, 15f, 15f) // Temperature 4–10 optimal
* RangeProbability(humidity, 6f, 8f, 15f, 15f) // Humidity 6+ optimal
* RangeProbability(y, 0f, 0f, 82f, 87f); // Height < 82
}
// RangeProbability: Trapezoidal probability function
// v < a: 0
// a <= v < b: Linear rise 0→1
// b <= v <= c: 1 (optimal zone)
// c < v <= d: Linear fall 1→0
// v > d: 0
Other Vegetation Generation¶
| Vegetation Type | Condition | Notes |
|---|---|---|
| Tall Grass | Humidity ≥ 6 | Grassy surface |
| Flowers | Humidity ≥ 6, random | Low probability |
| Cactus | Temperature > 8, Humidity < 6 | Desert regions |
| Pumpkin | Temperature > 6, Humidity ≥ 10 | Humid regions |
| Seagrass / Kelp | Near shore | Underwater vegetation |
| Ivy | Temperature ≥ 10, Humidity ≥ 10 | Tropical humid regions |
8.5 Snow and Ice Generation¶
Handled in the GenerateSnowAndIce method:
void GenerateSnowAndIce(TerrainChunk chunk) {
for (int i = 0; i < 16; i++) {
for (int j = 0; j < 16; j++) {
int temperature = chunk.GetTemperatureFast(i, j);
// Check if frozen (temperature ≤ 8 and sufficient height)
if (!SubsystemWeather.IsPlaceFrozen(temperature, height)) {
continue;
}
if (block is WaterBlock) {
// Freeze water surface
chunk.SetCellValueFast(i, height, j, 62); // Ice
// Possibly cover with snow
if (ShaftHasSnowOnIce(x, z)) {
chunk.SetCellValueFast(i, height + 1, j, 61); // Snow
}
}
else if (CanSupportSnow(block)) {
// Cover land with snow
chunk.SetCellValueFast(i, height + 1, j, 61); // Snow
}
}
}
}
8.6 Biome Transitions and Smoothing¶
Because continuous noise functions are used, biomes transition naturally into one another:
graph LR
A[Desert] -->|Temperature Decreases| B[Grassland]
B -->|Temperature Continues Decreasing| C[Forest]
C -->|Very Low Temperature| D[Tundra]
A -->|Humidity Increases| E[Savanna]
D -->|Humidity Decreases| F[Permafrost]
Key Transition Parameters:
- TGBiomeScaling: Controls noise frequency; higher values produce larger biomes
- m_temperatureOffset / m_humidityOffset: Random offsets that ensure each world has a unique biome distribution
8.7 Terrain Height and Biome Relationship¶
float CalculateHeight(float x, float z) {
float mountainFactor = CalculateMountainRangeFactor(x, z);
// Mountain factor influences terrain height
float hillsStrength = TGHillsStrength * Squish(mountainFactor, 0.72f, 0.89f);
float mountainsStrength = TGMountainsStrength * Squish(mountainFactor, 0.89f, 1.0f);
// Humidity influence (high humidity lowers mountains)
float humidityFactor = mountainFactor - 0.01f * humidity;
// Final height
return baseHeight + hillsStrength * hillsNoise + mountainsStrength * mountainsNoise;
}
8.8 Mod Extension Points¶
Mods can customize biomes in the following ways:
-
TerrainContentsGenerator24Initialize hook: Modify generation parameters
-
Add new ChunkGenerationStep:
-
PlantsManager extension: Add new tree types
Summary¶
Survivalcraft's terrain system is a carefully designed, high-performance voxel engine with the following characteristics:
- Layered architecture: Data, management, and generation layers have clearly defined responsibilities
- State machine driven: Chunks use state transitions for progressive loading
- Multi-threaded updates: Main and update threads synchronize via events
- Efficient storage: RLE + Deflate compression, organized into region files
- Flexible rendering: Multi-pass rendering supports various materials and transparency modes
- Implicit biomes: Natural biome transitions achieved through temperature / humidity / mountain factor combinations
Understanding these core mechanisms is valuable for mod development, performance optimization, or feature extension.